﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Management;

namespace System.Peppers.Wmi
{
    /// <summary>
    /// The Win32_LogicalDisk WMI class represents a data source that resolves to an actual local storage device on a computer system running Windows.
    /// </summary>
    public sealed class LogicalDisk : CimSetting
    {
        #region Static
        private static class WmiProperties
        {
            public const string Access = "Access",
                BlockSize = "BlockSize",
                Compressed = "Compressed",
                DeviceID = "DeviceID",
                DriveType = "DriveType",
                FileSystem = "FileSystem",
                FreeSpace = "FreeSpace",
                MaximumComponentLength = "MaximumComponentLength",
                MediaType = "MediaType",
                Name = "Name",
                NumberOfBlocks = "NumberOfBlocks",
                PNPDeviceID = "PNPDeviceID",
                ProviderName = "ProviderName",
                Size = "Size",
                VolumeDirty = "VolumeDirty",
                VolumeName = "VolumeName",
                VolumeSerialNumber = "VolumeSerialNumber";
        }

        private static class WmiMethods
        {
            public const string Chkdsk = "Chkdsk",
                ExcludeFromAutochk = "ExcludeFromAutochk",
                ScheduleAutoChk = "ScheduleAutoChk";
        }

        private static class WmiParameters
        {
            public const string FixErrors = "FixErrors",
                VigorousIndexCheck = "VigorousIndexCheck",
                SkipFolderCycle = "SkipFolderCycle",
                ForceDismount = "ForceDismount",
                RecoverBadSectors = "RecoverBadSectors",
                OKToRunAtBootUp = "OKToRunAtBootUp",
                LogicalDisk = "LogicalDisk";
        }

        /// <summary>
        /// Enumerates logical disks on the system
        /// </summary>
        /// <returns>List of logical disks</returns>
        public static IEnumerable<LogicalDisk> GetDisks()
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_LogicalDisk))
            {
                yield return new LogicalDisk(item);
            }
        }

        /// <summary>
        /// Enumerates logical disks on a remote system
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <returns>List of logical disks</returns>
        public static IEnumerable<LogicalDisk> GetDisks(WmiRemoteParameters remoteParams)
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_LogicalDisk, remoteParams))
            {
                yield return new LogicalDisk(item);
            }
        }
        #endregion

        internal LogicalDisk(ManagementObject wmiObject)
            : base(wmiObject)
        { }

        /// <summary>
        /// Type of media access available.
        /// </summary>
        public LogicalDiskAccess Access
        {
            get { return (LogicalDiskAccess)Convert.ToUInt16(this[WmiProperties.Access] ?? 0); }
        }

        /// <summary>
        /// Size, in bytes, of the blocks that form this storage extent. If unknown or if a block concept is not valid (for example, for aggregate extents, memory or logical disks), enter 1.
        /// </summary>
        public ulong BlockSize
        {
            get { return Convert.ToUInt64(this[WmiProperties.BlockSize] ?? 0); }
        }

        /// <summary>
        /// If True, the logical volume exists as a single compressed entity, such as a DoubleSpace volume. If file based compression is supported, such as on NTFS, this property is False.
        /// </summary>
        public bool Compressed
        {
            get { return Convert.ToBoolean(this[WmiProperties.Compressed] ?? false); }
        }

        /// <summary>
        /// Unique identifier of the logical disk from other devices on the system.
        /// </summary>
        public string DeviceID
        {
            get { return Convert.ToString(this[WmiProperties.DeviceID]); }
        }

        /// <summary>
        /// Numeric value that corresponds to the type of disk drive this logical disk represents.
        /// </summary>
        public LogicalDiskDriveType DriveType
        {
            get { return (LogicalDiskDriveType)Convert.ToUInt32(this[WmiProperties.DriveType] ?? 0); }
        }

        /// <summary>
        /// File system on the logical disk.  Example: "NTFS"
        /// </summary>
        public string FileSystem
        {
            get { return Convert.ToString(this[WmiProperties.FileSystem]); }
        }

        /// <summary>
        /// Space, in bytes, available on the logical disk.
        /// </summary>
        public ulong FreeSpace
        {
            get { return Convert.ToUInt64(this[WmiProperties.FreeSpace] ?? 0); }
        }

        /// <summary>
        /// Maximum length of a filename component supported by the Windows drive. A filename component is that portion of a filename between backslashes. The value can be used to indicate that long names are supported by the specified file system. For example, for a FAT file system supporting long names, the function stores the value 255, rather than the previous 8.3 indicator. Long names can also be supported on systems that use the NTFS file system.
        /// </summary>
        public uint MaximumComponentLength
        {
            get { return Convert.ToUInt32(this[WmiProperties.MaximumComponentLength] ?? 0); }
        }

        /// <summary>
        /// Type of media currently present in the logical drive. This value will be one of the values of the MEDIA_TYPE enumeration defined in Winioctl.h. The value may not be exact for removable drives if currently there is no media in the drive.
        /// </summary>
        public LogicalDiskMediaType MediaType
        {
            get { return (LogicalDiskMediaType)Convert.ToUInt32(this[WmiProperties.MediaType] ?? 0); }
        }

        /// <summary>
        /// Label by which the object is known. 
        /// </summary>
        public string Name
        {
            get { return Convert.ToString(this[WmiProperties.Name]); }
        }

        /// <summary>
        /// Total number of consecutive blocks, each block the size of the value contained in the BlockSize property, which form this storage extent. Total size of the storage extent can be calculated by multiplying the value of the BlockSize property by the value of this property. If the value of BlockSize is 1, this property is the total size of the storage extent.
        /// </summary>
        public ulong NumberOfBlocks
        {
            get { return Convert.ToUInt64(this[WmiProperties.NumberOfBlocks] ?? 0); }
        }

        /// <summary>
        /// Windows Plug and Play device identifier of the logical device. 
        /// </summary>
        public string PlugAndPlayDeviceID
        {
            get { return Convert.ToString(this[WmiProperties.PNPDeviceID]); }
        }

        /// <summary>
        /// Network path to the logical device.
        /// </summary>
        public string NetworkPath
        {
            get { return Convert.ToString(this[WmiProperties.ProviderName]); }
        }

        /// <summary>
        /// Size of the disk drive in bytes.
        /// </summary>
        public ulong Size
        {
            get { return Convert.ToUInt64(this[WmiProperties.Size] ?? 0); }
        }

        /// <summary>
        /// If True, the disk requires ChkDsk to be run at the next restart. This property is only applicable to those instances of logical disk that represent a physical disk in the machine.  It is not applicable to mapped logical drives.
        /// </summary>
        public bool VolumeDirty
        {
            get { return Convert.ToBoolean(this[WmiProperties.VolumeDirty] ?? false); }
        }

        /// <summary>
        /// Volume name of the logical disk.  Constraints: Maximum 32 characters.
        /// </summary>
        public string VolumeName
        {
            get { return Convert.ToString(this[WmiProperties.VolumeName]); }
            set { this[WmiProperties.VolumeName] = value; }
        }

        /// <summary>
        /// Volume serial number of the logical disk.  Constraints: Maximum 11 characters.
        /// </summary>
        public string VolumeSerialNumber
        {
            get { return Convert.ToString(this[WmiProperties.VolumeSerialNumber]); }
        }

        /// <summary>
        /// The Chkdsk instance method invokes the chkdsk operation on the disk. This method is only applicable to those instances of logical disk that represent a physical disk in the machine. It is not applicable to mapped logical drives.
        /// </summary>
        /// <param name="fixErrors">Indicates what should be done to errors found on the disk. If true, then errors are fixed. The default is false.</param>
        /// <param name="vigorousIndexCheck">If true, a vigorous check of index entries should be performed. The default is true.</param>
        /// <param name="skipFolderCycle">If true, the folder cycle checking should be skipped. The default is true.</param>
        /// <param name="forceDismount">If true, the drive should be forced to dismount before checking. The default is false.</param>
        /// <param name="recoverBadSector">If true, the bad sectors should be located and the readable information should be recovered from these sectors. The default is false.</param>
        /// <param name="okToRunAtBootUp">If true, the chkdsk operation should be performed at next boot up time, in case the operation could not be performed because the disk is locked at time this method is called. The default is false.</param>
        /// <returns>Result of chkdsk operation</returns>
        public CheckDiskResult CheckDisk(bool fixErrors, bool vigorousIndexCheck, bool skipFolderCycle, bool forceDismount, bool recoverBadSector, bool okToRunAtBootUp)
        {
            return (CheckDiskResult)ExecuteMethodWithResult(WmiMethods.Chkdsk, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.FixErrors] = fixErrors;
                inParam[WmiParameters.VigorousIndexCheck] = vigorousIndexCheck;
                inParam[WmiParameters.SkipFolderCycle] = skipFolderCycle;
                inParam[WmiParameters.ForceDismount] = forceDismount;
                inParam[WmiParameters.RecoverBadSectors] = recoverBadSector;
                inParam[WmiParameters.OKToRunAtBootUp] = okToRunAtBootUp;
            });
        }

        /// <summary>
        /// The ExcludeFromAutochk method excludes disks from the autochk operation to be run at the next reboot. If not excluded, autochk is performed on the disk when the dirty bit is set for the disk. Note that the calls to exclude disks are not cumulative. If a call is made to exclude some disks, then the new list is not added to the list of disks that are already marked for exclusion. The new list of disks overwrites the previous list. This method is only applicable to those instances of logical disk that represent a physical disk in the machine. It is not applicable to mapped logical drives. 
        /// </summary>
        /// <param name="logicalDisks">List of drives that should be excluded from autochk at the next reboot. The string syntax consists of the drive letter followed by a colon for the logical disk.</param>
        /// <returns>Result of setting autocheck</returns>
        public SetAutoCheckResult ExcludeFromAutoCheck(string[] logicalDisks)
        {
            return (SetAutoCheckResult)ExecuteMethodWithResult(WmiMethods.ExcludeFromAutochk, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.LogicalDisk] = logicalDisks;
            });
        }

        /// <summary>
        /// The ScheduleAutoChk class method schedules Autochk to be run on the disk drive represented by the Win32_LogicalDisk at the next reboot if the dirty bit is set. This method is only applicable to those instances of logical disk that represent a physical disk in the machine. This method is not applicable to mapped logical drives.
        /// </summary>
        /// <param name="logicalDisks">Specifies the list of drives to schedule for Autochk at the next reboot. The string syntax consists of the drive letter followed by a colon for the logical disk, for example: "C:"</param>
        /// <returns>Result of setting autocheck</returns>
        public SetAutoCheckResult ScheduleAutoCheck(string[] logicalDisks)
        {
            return (SetAutoCheckResult)ExecuteMethodWithResult(WmiMethods.ScheduleAutoChk, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.LogicalDisk] = logicalDisks;
            });
        }
    }
}
